﻿#region Copyright © 2011 Prageeth Silva <prageeth@thenewcoders.com>
/*
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the author(s) be held liable for any damages arising from
 * the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 *   1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 * 
 *   2. Altered source versions must be plainly marked as such, and must not
 *      be misrepresented as being the original software.
 * 
 *   3. This notice may not be removed or altered from any source distribution.
 */
#endregion

using System;
using System.ComponentModel;
using System.IO;
using SourcePlus.Core;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace SourcePlus.RtfPdf
{

    public class PdfMerger
    {

        public event ProgressChangedEventHandler ProgressChanged;

        public event MergeCompletedEventHandler MergeCompleted;
                
        public SourceFile[] SourceFiles { get; private set; }

        public DestinationFile DestinationFile { get; private set; }

        public bool SkipUnreadableFiles { get; set; }
        
        public bool OverwriteDestination { get; set; }

        public int Failed { get; private set; }

        public int Successful { get; private set; }

        public bool IsBusy 
        { 
            get { return this.worker.IsBusy; }
        }


        private BackgroundWorker worker = new BackgroundWorker();


        public PdfMerger()
            : base()
        {

            this.SourceFiles = new SourceFile[] { };
            this.DestinationFile = null;
            this.SkipUnreadableFiles = false;
            this.OverwriteDestination = false;
            this.Failed = 0;
            this.Successful = 0;

            this.worker.WorkerReportsProgress = true;
            this.worker.WorkerSupportsCancellation = true;
            this.worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            this.worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            
            // check for input validity
            if (this.SourceFiles == null || this.SourceFiles.Length == 0)
            {
                throw new Exception("Invalid or empty list of source files!");
            }
            if (this.DestinationFile == null)
            {
                throw new Exception("Invalid destination file!");
            }

            // check if the destination file is also in the source list
            foreach (SourceFile s in this.SourceFiles)
            {
                if (s.LongName.ToLower() == this.DestinationFile.LongName.ToLower())
                {
                    throw new Exception("The destination file is also included in the source list!");
                }
            }

            // check if the destination file exists?
            if (File.Exists(this.DestinationFile.LongName) && !this.OverwriteDestination)
            {
                throw new Exception("The destination file already exists! Select a new destination.");
            }

            // create a reader for the first document, or keep trying to open the files
            int first = 0;
            int totalPages = 0;
            PdfReader reader = null;
            while (first < this.SourceFiles.Length)
            {
                try
                {
                    reader = new PdfReader(this.SourceFiles[first].LongName);
                    this.Successful++;
                    // file properly opened, so break out of the while loop
                    break;
                }
                catch
                {
                    this.Failed++;
                    // try next file?
                    if (this.SkipUnreadableFiles)
                    {
                        first++;
                        continue;
                    }
                    else
                    {
                        throw new Exception("File '" + this.SourceFiles[first] + "' cannot be accessed!");
                    }
                }
            }

            // check if we have alteast one file opened.
            if (reader == null)
            {
                throw new Exception("All source files were inaccessible!");
            }

            // get the total number of pages in the first file
            int pages = reader.NumberOfPages;

            // create the document object and a writer
            Document document = new Document(reader.GetPageSizeWithRotation(1));
            PdfWriter writer = null;
            try
            {
                writer = PdfWriter.GetInstance(document, new FileStream(this.DestinationFile.LongName, FileMode.Create));
                document.Open();
            }
            catch
            {
                throw new Exception("Cannot create destination file '" + this.DestinationFile + "'!");
            }

            PdfContentByte cb = writer.DirectContent;
            PdfImportedPage page;

            // merge the contents
            for (int f = first; f < this.SourceFiles.Length; f++)
            {

                // check if user has cancelled action
                if (this.worker.CancellationPending)
                {
                    e.Cancel = true;
                    e.Result = null;
                    document.Close();
                    return;
                }

                // first file is already opened
                if (f > 0)
                {
                    // try to open the next file
                    while (f < this.SourceFiles.Length)
                    {
                        try
                        {
                            reader = new PdfReader(this.SourceFiles[f].LongName);
                            this.Successful++;
                            // file properly opened, so break out of the while loop
                            break;
                        }
                        catch
                        {
                            this.Failed++;
                            // try next file?
                            if (this.SkipUnreadableFiles)
                            {
                                continue;
                            }
                            else
                            {
                                // try to close the opened document
                                try
                                {
                                    document.Close();
                                }
                                catch { }
                                throw new Exception("File '" + this.SourceFiles[f] + "' cannot be accessed!");
                            }
                        }
                    }
                    pages = reader.NumberOfPages;
                }

                // copy the pages of the currently opened document
                for (int p = 1; p <= pages; p++)
                {

                    document.SetPageSize(reader.GetPageSizeWithRotation(p));
                    document.NewPage();
                    page = writer.GetImportedPage(reader, p);

                    // adopt for page orientation
                    int rotation = reader.GetPageRotation(p);
                    if (rotation == 90 || rotation == 270)
                    {
                        cb.AddTemplate(page, 0, -1f, 1f, 0, 0, reader.GetPageSizeWithRotation(p).Height);
                    }
                    else
                    {
                        cb.AddTemplate(page, 1f, 0, 0, 1f, 0, 0);
                    }

                    // increment the total number of pages
                    totalPages++;

                }

                // increment progress
                this.worker.ReportProgress((int)((100.0f)*(f + 1.0f) / this.SourceFiles.Length));

            }

            // close the document
            document.Close();

            // send the total number of pages
            e.Result = totalPages;
            
        }
        
        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.OnProgressChanged(sender, e);
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.OnMergeCompleted(sender, new MergeCompletedEventArgs(e));
        }
        
        protected virtual void OnProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (this.ProgressChanged != null)
            {
                this.ProgressChanged(this, e);
            }
        }

        protected virtual void OnMergeCompleted(object sender, MergeCompletedEventArgs e)
        {
            if (this.MergeCompleted != null)
            {
                this.MergeCompleted(this, e);
            }
        }

        public bool MergeFiles(DestinationFile destinationFile, SourceFile[] sourceFiles)
        {

            if (!this.worker.IsBusy)
            {

                this.DestinationFile = destinationFile;
                this.SourceFiles = sourceFiles;

                this.Failed = 0;
                this.Successful = 0;
            
                this.worker.RunWorkerAsync();

                return true;

            }
            return false;

        }

        public void CancelMerging()
        {

            if (this.worker.IsBusy)
            {
                this.worker.CancelAsync();
            }

        }

        public static int CountPages(string strFileName)
        {
            
            PdfReader reader = new PdfReader(strFileName);
            int pages = reader.NumberOfPages;
            reader.Close();
            return pages;

        }

    }

    #region "Custom event Handlers"

    public delegate void MergeCompletedEventHandler(object sender, MergeCompletedEventArgs e);

    public class MergeCompletedEventArgs : ActionCompletedEventArgs
    {

        public MergeCompletedEventArgs(AsyncCompletedEventArgs parent)
            : base(parent)
        {

        }

        public MergeCompletedEventArgs(RunWorkerCompletedEventArgs parent)
            : base(parent)
        {

        }

    }

    #endregion

}